/*
 * Created on 2008-2-11
 *
 * TODO 
 */
package jacky.lanlan.song.reflection.bean.editor;

import jacky.lanlan.song.closure.ReturnableHandler;
import jacky.lanlan.song.util.cache.ConcurrentCache;

import java.awt.Color;
import java.awt.Font;
import java.beans.PropertyEditor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import sun.beans.editors.*;

/**
 * 比Sun的 {@link java.beans.PropertyEditorManager}
 * 性能更高，而且线程安全的PropertyEditorManager。
 * <p>
 * 默认注册的Editor类型，除了Sun的{@link java.beans.PropertyEditorManager}所提供的类型之外，
 * 还有其他一些实用的类型，具体见本包的PropertyEditor类。
 * </p>
 * @author Jacky.Song
 * @since 1.5
 */
public class PropertyEditorManager {
	private static final Logger logger=Logger.getLogger(PropertyEditorManager.class);
	
	private static final ConcurrentCache<Class<?>, PropertyEditor,PropertyEditor> EDITOR_CACHE = 
		new ConcurrentCache<Class<?>, PropertyEditor,PropertyEditor>(
				new ReturnableHandler<PropertyEditor,PropertyEditor>(){
					public PropertyEditor doWith(PropertyEditor data) {
						return data;
					}
				}
		);
	
	private static String[] searchPath;

	static {
		try {
			//装载Sun默认的Editor
			EDITOR_CACHE.cacheIfAbsent(String.class, new StringEditor());
			EDITOR_CACHE.cacheIfAbsent(Color.class, new ColorEditor());
			EDITOR_CACHE.cacheIfAbsent(Font.class, new FontEditor());
			
			/////////////// 装载自定义Editor(大部分来自Spring) ///////////////
			
			// The JDK does not contain default editors for number wrapper types!
			// Override JDK primitive number editors with our own CustomNumberEditor.
			EDITOR_CACHE.cacheIfAbsent(byte.class, new CustomNumberEditor(Byte.class, false));
			EDITOR_CACHE.cacheIfAbsent(Byte.class, new CustomNumberEditor(Byte.class, true));
			EDITOR_CACHE.cacheIfAbsent(short.class, new CustomNumberEditor(Short.class, false));
			EDITOR_CACHE.cacheIfAbsent(Short.class, new CustomNumberEditor(Short.class, true));
			EDITOR_CACHE.cacheIfAbsent(int.class, new CustomNumberEditor(Integer.class, false));
			EDITOR_CACHE.cacheIfAbsent(Integer.class, new CustomNumberEditor(Integer.class, true));
			EDITOR_CACHE.cacheIfAbsent(long.class, new CustomNumberEditor(Long.class, false));
			EDITOR_CACHE.cacheIfAbsent(Long.class, new CustomNumberEditor(Long.class, true));
			EDITOR_CACHE.cacheIfAbsent(float.class, new CustomNumberEditor(Float.class, false));
			EDITOR_CACHE.cacheIfAbsent(Float.class, new CustomNumberEditor(Float.class, true));
			EDITOR_CACHE.cacheIfAbsent(double.class, new CustomNumberEditor(Double.class, false));
			EDITOR_CACHE.cacheIfAbsent(Double.class, new CustomNumberEditor(Double.class, true));
			EDITOR_CACHE.cacheIfAbsent(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
			EDITOR_CACHE.cacheIfAbsent(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));

			// The JDK does not contain a default editor for char!
			EDITOR_CACHE.cacheIfAbsent(char.class, new CharacterEditor(false));
			EDITOR_CACHE.cacheIfAbsent(Character.class, new CharacterEditor(true));
			
			// Default editors for primitive arrays.
			EDITOR_CACHE.cacheIfAbsent(byte[].class, ByteArrayPropertyEditor.INSTANCE);
			EDITOR_CACHE.cacheIfAbsent(char[].class, CharArrayPropertyEditor.INSTANCE);
			
			// 其他Editor
			EDITOR_CACHE.cacheIfAbsent(Date.class, DateEditor.INSTANCE);
			EDITOR_CACHE.cacheIfAbsent(Class.class, new ClassEditor());
			EDITOR_CACHE.cacheIfAbsent(Pattern.class, new PatternEditor());
			EDITOR_CACHE.cacheIfAbsent(Properties.class, PropertiesEditor.INSTANCE);
			EDITOR_CACHE.cacheIfAbsent(ResourceBundle.class, ResourceBundleEditor.INSTANCE);
			EDITOR_CACHE.cacheIfAbsent(String[].class, new StringArrayPropertyEditor());
			EDITOR_CACHE.cacheIfAbsent(Class[].class, new ClassArrayEditor());
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Change the list of package names that will be used for finding property
	 * editors.
	 * <p>
	 * First, if there is a security manager, its
	 * <code>checkPropertiesAccess</code> method is called. This could result in
	 * a SecurityException.
	 * 
	 * @param path
	 *          Array of package names.
	 * @exception SecurityException
	 *              if a security manager exists and its
	 *              <code>checkPropertiesAccess</code> method doesn't allow
	 *              setting of system properties.
	 * @see SecurityManager#checkPropertiesAccess
	 */
	public static void setEditorSearchPath(String... path) {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
			sm.checkPropertiesAccess();
		}
		if (path == null) {
			path = new String[0];
		}
		searchPath = path;
	}

	/**
	 * Gets the package names that will be searched for property editors.
	 * 
	 * @return The array of package names that will be searched in order to find
	 *         property editors.
	 *         <p>
	 *         The default value for this array is implementation-dependent, e.g.
	 *         Sun implementation initially sets to {"sun.beans.editors"}.
	 */
	public static String[] getEditorSearchPath() {
		// Return a copy of the searchPath.
		String[] copy = null;
		synchronized (searchPath) {
			System.arraycopy(searchPath, 0, copy, 0, searchPath.length);
		}
		return copy;
	}

	/**
	 * Register an editor to be used to edit values of a given target class.
	 * <p>
	 * First, if there is a security manager, its
	 * <code>checkPropertiesAccess</code> method is called. This could result in
	 * a SecurityException.
	 * 
	 * @param targetType
	 *          the Class object of the type to be edited
	 * @param editor
	 *          the editor . If this is null, then any
	 *          existing definition will be removed.
	 * @exception SecurityException
	 *              if a security manager exists and its
	 *              <code>checkPropertiesAccess</code> method doesn't allow
	 *              setting of system properties.
	 * @see SecurityManager#checkPropertiesAccess
	 */
	public static void registerEditor(Class<?> targetType,PropertyEditor editor) {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
			sm.checkPropertiesAccess();
		}
		if (editor == null) {
			EDITOR_CACHE.remove(targetType);
		} else {
			try {
				EDITOR_CACHE.cacheIfAbsent(targetType, editor);
			}
			catch (InterruptedException e) {
				logger.error("缓存 PropertyEditor ["+editor+"] 失败",e);
				throw new RuntimeException("缓存 PropertyEditor ["+editor+"] 失败",e);
			}
		}
	}

	/**
	 * Locate a value editor for a given target type.
	 * 
	 * @param targetType
	 *          The Class object for the type to be edited
	 * @return An editor object for the given target class. The result is null if
	 *         no suitable editor can be found.
	 */
	@SuppressWarnings("unchecked")
	public static PropertyEditor findEditor(Class<?> targetType) {
		if (EDITOR_CACHE.containsKey(targetType)) {
			return EDITOR_CACHE.get(targetType);
		}

		Class<? extends PropertyEditor> editorClass=null;
		PropertyEditor pe=null;

		// Now try adding "Editor" to the class name.
		StringBuilder editorName = new StringBuilder(targetType.getName()).append("Editor");
		try {
			editorClass=(Class) Class.forName(editorName.toString());
			pe=editorClass.newInstance();
			EDITOR_CACHE.cacheIfAbsent(targetType, pe);
			return pe;
		}
		catch (Exception ex) {
			logger.debug("不能创建 PropertyEditor ["+editorName+"] ",ex);
		}
		
		// Now try looking for <searchPath>.fooEditor
		String targetTypeName=targetType.getSimpleName();
		for (int i = 0; i < searchPath.length; i++) {
			editorName.delete(0, editorName.length());
			editorName.append(searchPath[i]).append(".").append(targetTypeName).append("Editor");
			try {
				editorClass=(Class) Class.forName(editorName.toString());
				pe=editorClass.newInstance();
				EDITOR_CACHE.cacheIfAbsent(targetType, pe);
				return pe;
			}
			catch (Exception ex) {
				logger.debug("不能创建 PropertyEditor ["+editorName+"] ",ex);
			}
		}

		// We couldn't find a suitable Editor.
		return null;
	}

}
